/* 
 * Copyright 2007 Parabon Computation, Inc.
 * 
 * Parabon Computation, Inc, grant you ("Licensee") a non-exclusive, royalty 
 * free, license to use, and redistribute this software in source and binary 
 * code form, provided that i) this copyright notice and license appear on 
 * all copies of the software; ii) Licensee does not utilize the software in 
 * a manner which is disparaging to Parabon and iii) Licensee does NOT 
 * modify the software in any way. 
 * 
 * This software is provided "AS IS," without a warranty of any kind. ALL 
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING 
 * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 
 * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. PARABON AND ITS LICENSORS SHALL NOT 
 * BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, 
 * MODIFYING OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT 
 * WILL PARABON OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, 
 * OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE 
 * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, 
 * ARISING OUT OF THE USE OF OR INABILITY TO USE SOFTWARE, EVEN IF PARABON HAS 
 * BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 
 * 
 * This software is not designed or intended for use in on-line control of 
 * aircraft, air traffic, aircraft navigation or aircraft communications; or 
 * in the design, construction, operation or maintenance of any nuclear 
 * facility. Licensee represents and warrants that it will not use or 
 * redistribute the Software for such purposes. 
 */

import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.HashMap;

import com.parabon.client.*;
import com.parabon.io.InvalidLoginException;
import java.util.Calendar;
import java.net.InetAddress;

/**
 * This application computes the square of a single number. The square is
 * computed using an external native application. This application demonstrates
 * the use of the FrontierAPI to execute native codes.
 */

public class NativeApp implements TaskResultListener, TaskExceptionListener {
  public static final String JOB_TYPE_ATTRIBUTE         = "JobType";
  public static final String JOB_NAME_ATTRIBUTE         = "JobName";
  
  public static final String JOB_TYPE = "NativeApp";
  public static final String DEFAULT_JOB_NAME = "DefaultNativeApp";

  public static final String EXECUTABLE_ELEMENT_FILENAME = "nativeTask.jar";
  public String nativeBundleName = "square.jar"; 


  private String jobName;
  private SessionManager manager;
  private Job job;
  private boolean taskComplete = false;

  private NativeApp(String jobName) {
    this.jobName = jobName;
    createSession();
  }
  
  private void createSession() {
    System.out.println("Connecting to session manager");
    try {
      manager = SessionManager.getInstance();
      manager.reestablish();
      System.out.println("Connected via " + manager);
    }
    catch (ServerCommunicationException e) {
      System.err.println("Unable to establish connection to server");
      System.err.println(e);
      System.exit(1);
    }
    catch (InvalidLoginException e) {
      System.err.println("Login failed");
      System.err.println(e);
      System.exit(1);
    }
  }
  
  /**
   * Closes the connection to the server
   */
  public void closeSession() {
    if (manager != null) {
      System.out.println("Disconnecting from server");
      manager.destroy();
      manager = null;
      System.out.println("Disconnected");
    }
  }

  /**
   * Finds the job in the current session
   */
  public boolean findJob() {
    assert(job == null);
    
    for (Job currJob : manager.getJobs()) {
      String currJobType = currJob.getAttributes().get(JOB_TYPE_ATTRIBUTE);
      String currJobName = currJob.getAttributes().get(JOB_NAME_ATTRIBUTE);
      if ((currJobType != null) && currJobType.equals(JOB_TYPE) &&
          (currJobName != null) && currJobName.equals(jobName)) {
        job = currJob;
        return true;
      }
    }

    return false;
  }

  public void launch(boolean addListener) throws IOException {
    assert(job == null);
    
    System.out.println("Creating " + JOB_TYPE + " with name " + jobName);
    
    Map<String, String> jobAttributes = new HashMap<String, String>();
    jobAttributes.put(JOB_TYPE_ATTRIBUTE, JOB_TYPE);
    jobAttributes.put(JOB_NAME_ATTRIBUTE, jobName);
    job = manager.createJob(jobAttributes);

    File demoDirectory = new File(System.getProperty("demo.home", "."));
    File taskJarFile = new File(demoDirectory, EXECUTABLE_ELEMENT_FILENAME);
    String taskJarID = null;
    try {
      taskJarID = job.addJavaExecutableElement(taskJarFile);
    }
    catch (IOException e) {
      System.err.println("Can't access task jar file at " + taskJarFile);
      System.exit(0);
    }


    // Add native data element containing square executable to job
    File nativeBundleFile = new File(demoDirectory + File.separator + nativeBundleName);
    String  dataElementID  = job.addDataElement( nativeBundleFile, ElementPacking.PARABON_NATIVE_JAR );
    
    if (addListener) {
      addListener();
    }
    
    //
    // Start the task
    //
    SerializableTaskSpec taskSpec = new SerializableTaskSpec();
    taskSpec.addRequiredExecutableElement(taskJarID);
    DataElementProxy dataElementProxy = new DataElementProxy(dataElementID);
    taskSpec.setTask(new NativeTask(dataElementProxy, "square", 55));
    TaskProxy task = job.addTask(taskSpec, new HashMap<String,String>());
    task.start();

    Calendar cal=Calendar.getInstance();
    String date=(cal.getTime()).toString();
    System.out.println("Created task at time:"+date);
  }

  public void addListener() {
    job.addListener(this);
  }
  
  public synchronized void waitUntilComplete() {
    System.out.println("Waiting for results...");
    while (!taskComplete) {
      try {
        wait();
      }
      catch (InterruptedException e) {}
    }
  }
    
  /**
   * Destroys the job and all tasks in the job
   */
  public void remove() {
    if (job != null) {
      job.remove();
      job = null;
    }
  }

  private synchronized void taskComplete() {
    taskComplete = true;
    notifyAll();
  }

  public void resultsPosted(TaskResultEvent event) {
    NativeTask.Result result = (NativeTask.Result)event.getResultsObject();

    System.out.println("The square is " + result.getSquare());
    System.out.println("Hostname is " + result.getHostname());
    System.out.println("Time when landed on compute engine is " + result.getEntertime());
    System.out.println();

    taskComplete();
    Calendar finalcal=Calendar.getInstance();
    String finaldate=(finalcal.getTime()).toString();
    System.out.println("Results got at time:"+finaldate);
  }

  public void exceptionThrown(TaskExceptionEvent event) {
    System.out.println(
      "Exception (" + event.getCode() + ")" + " while running task");
    System.out.println(event.getDescription());
    System.out.println();

    taskComplete();
  }
  
  public static void usage() {
    System.err.println("Usage: " + NativeApp.class + " (launch|listen|remove)");
  }

  public static void main(String[] args) throws IOException {
    boolean launch = false;
    boolean listen = false;
    boolean remove = false;
    
    if (args.length == 0) {
      usage();
      System.exit(1);
    }
    for (String arg : args) {
      if (arg.equals("launch")) {
        launch = true;
      } else if (arg.equals("listen")) {
        listen = true;
      } else if (arg.equals("remove")) {
        remove = true;
      } else {
        usage();
        System.exit(1);
      }
    }
    
    String jobName = System.getProperty("jobName", DEFAULT_JOB_NAME);
    NativeApp dataApp = new NativeApp(jobName);
    
    if (launch) {
      if (dataApp.findJob()) {
        System.err.println(
          "Job of type " + JOB_TYPE + " with name " + jobName +
          " already exists.");
        System.err.println("Use the \"remove\" mode to remove the job.");
        System.exit(1);
      }

      dataApp.launch(listen);
    } else {
      if (!dataApp.findJob()) {
        System.err.println(
          "Job of type " + JOB_TYPE + " with name " + jobName +
          " does not exist.");
        System.err.println("Use the \"launch\" mode to create a new job.");
        System.exit(1);
      }
    }
    
    if (listen) {
      if (!launch) {
        dataApp.addListener();
      }
      dataApp.waitUntilComplete();
    }
    
    if (remove) {
      dataApp.remove();
    }
    
    dataApp.closeSession();
  }
}
